/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package com.pictavore.image;

import com.google.inject.Inject;
import java.awt.image.*;
import java.io.*;
import java.awt.*;
import java.lang.Object.*;
import javax.imageio.*;
import javax.swing.*;

import java.awt.image.renderable.*;
import javax.media.jai.*;

import com.pictavore.image.fits.Fits;
import com.pictavore.image.fits.FITSException;
import com.pictavore.message.Messenger;
import com.pictavore.util.Utils;

/**
 *
 * @author dvenable
 */
public class PVImage
{
    Boolean IsFits;
    public String Source;
//    public BufferedImage BImage;
    public BufferedImage Thumbnail;
    public String Name;
    
//    public int[] Data;
//    public byte[] RawData;
    public int Width;
    public int Height;
    public int Length;
    public int NBits;
    public int NSeps;
    public float BZero;
    public float BScale;
    public String Info;
//    public int RawMinimum;
//    public int RawMaximum;
    public int Minimum;
    public int Maximum;
    
    public String tempData;     // location of file to store data when out of memory
    
    // private buffers that are that actual data buffers for BufferedImage data
    // TODO make accessors
    public BufferedImage src = null;
    public BufferedImage wrk = null;
    BufferedImage undo = null;
    
    public TiledImage src2 = null;
    public TiledImage wrk2 = null;
    TiledImage undo2 = null;
    
    WritableRaster srst = null;
    WritableRaster wrst = null;
    byte[] sbuf8 = null;
    short[] sbuf16 = null;
    byte[] wbuf8 = null;
    short[] wbuf16 = null;

    @Inject
    Messenger messenger;

    public PVImage(String srcPath) throws PVImageException
    {
        try
        {
            StringBuffer sb = new StringBuffer();

            if (Fits.IsFits(srcPath))
            {
                Fits fits = new Fits(srcPath);
                
                Width = fits.getWidth();
                Height = fits.getHeight();
                NBits = fits.getNBits();
                NSeps = 1;
                BZero = (float)fits.getBZero();
                BScale = (float)fits.getBScale();
//                RawMinimum = fits.getRawMin();
//                RawMaximum = fits.getRawMax();
                
//                src = fits.getBufferedImage();
                src2 = fits.getTiledImage();

                sb.append(String.format("Loaded file <%s>", srcPath));
                sb.append(fits.getInfo());
                sb.append(String.format("\nDims: %d x %d\n", fits.getWidth(), fits.getHeight()));
                sb.append(String.format("Min/max: %d / %d\n", fits.getMinValue(), fits.getMaxValue()));
                sb.append(String.format("Raw min/max: %d / %d\n", fits.getRawMin(), fits.getRawMax()));

                IsFits = true;
            }
            else
            {
//                src = ImageIO.read(new File(srcPath));
                ParameterBlock pb = new ParameterBlock();
                pb.add(srcPath);
                PlanarImage pimg = JAI.create("fileload", pb);
                src2 = new TiledImage(pimg, true);
                
                Width = src.getWidth();
                Height = src.getHeight();
                NBits = src.getColorModel().getPixelSize();
                NSeps = src.getColorModel().getNumComponents();
                BZero = 0f;
                BScale = 1.0f;

                sb.append(String.format("Loaded file <%s>", srcPath));
                sb.append(String.format("\nDims: %d x %d\n", Width, Height));
                IsFits = false;
            }

//            BImage = src;
            Info = sb.toString();
            Name = Utils.getFilenameWithoutExtension(srcPath);
            Length = Width * Height;
            Source = srcPath;

            // grab the src buffer
//            set_editable_bufs();
            set_editable_bufs2();

        } catch (Exception ex)
        {
            throw new PVImageException("Exception loading specified image: " + ex.getMessage());
        }
    }
    
    public PVImage(byte[] data, int nbits, int w, int h, String name) throws PVImageException
    {
        if (nbits == 16)
        {
            int len = w * h;
            DataBufferUShort db = new DataBufferUShort(len);
            short[] words = db.getData();
            for (int k=0, kk=0; k<len; ++k, kk+=2)
                words[k] = (short)((data[kk] & 0x00ff)<<8 | (data[kk+1] & 0x00ff));
                
            SampleModel sm = RasterFactory.createBandedSampleModel(DataBuffer.TYPE_USHORT, w, h, 1);
            ColorModel cm = PlanarImage.createColorModel(sm);
            Raster rst = RasterFactory.createWritableRaster(sm, db, new Point(0,0));
            src2 = new TiledImage(0,0,w,h,0,0,sm,cm);
            src2.setData(rst);

            Width = w;
            Height = h;
            NBits = nbits;
            NSeps = 1;
            BZero = 0f;
            BScale = 1.0f;
            IsFits = true;
            Name = name;
            Length = len;
            Source = name;

            set_editable_bufs2();
        }
        else if (nbits == 8)
        {
            throw new PVImageException("Loading 8 bit image from buffer is not implemented");
        }
    }
    

    // <editor-fold defaultstate="collapsed" desc="Undo operation - copy data from undo buffers">
//    public void undo(boolean doNotify)
//    {
//        if (undo == null)
//            return;
//
//        src = undo;
//        undo = null;
//        set_editable_bufs();
//
//        if (doNotify)
//            Messenger.SendMessage("ImageUpdated", this, this);
//    }

    public void undo2(boolean doNotify)
    {
        if (undo2 == null)
            return;

        src2 = undo2;
        undo2 = null;
        set_editable_bufs2();

        if (doNotify)
            messenger.sendMessage(ImageMessage.IMAGE_UPDATED, this, new Object[] { this });
    }
    // </editor-fold>
    
    // <editor-fold defaultstate="collapsed" desc="copy image: BufferedImage-based">
//    static public BufferedImage copyImage(BufferedImage src)
//    {
//        WritableRaster drst = src.copyData(null);
//        return new BufferedImage(src.getColorModel(), drst, src.isAlphaPremultiplied(), null);
//    }

    // </editor-fold>
    
    // <editor-fold defaultstate="collapsed" desc="copy image: JAI-based">
    static public TiledImage copyImage(TiledImage src)
    {
        return new TiledImage(src, false);
    }

 // </editor-fold>
    
    // <editor-fold defaultstate="collapsed" desc="setup editor buffers: JAI-based">
    void set_editable_bufs2()
    {
        wrk2 = copyImage(src2);
        int len = src2.getWidth() * src2.getHeight();

        // to change image, mod the buffer and do a src.setData(rst)
        srst = src2.getWritableTile(0,0);
        wrst = wrk2.getWritableTile(0,0);
        if (NBits == 8)
        {
            sbuf8 = ((DataBufferByte) srst.getDataBuffer()).getData();
            wbuf8 = ((DataBufferByte) wrst.getDataBuffer()).getData();
            Minimum = Maximum = (sbuf8[0] & 0x00ff);
            for (int k = 0; k < len; ++k)
            {
                int px = sbuf8[k] & 0x00ff;
                if (px < Minimum)
                {
                    Minimum = px;
                }
                if (px > Maximum)
                {
                    Maximum = px;
                }
            }
        } else if (NBits == 16)
        {
            sbuf16 = ((DataBufferUShort) srst.getDataBuffer()).getData();
            wbuf16 = ((DataBufferUShort) wrst.getDataBuffer()).getData();
            Minimum = Maximum = (sbuf16[0] & 0x0000ffff);
            for (int k = 0; k < len; ++k)
            {
                int px = sbuf16[k] & 0x0000ffff;
                if (px < Minimum)
                {
                    Minimum = px;
                }
                if (px > Maximum)
                {
                    Maximum = px;
                }
            }
        }
    }
    // </editor-fold>
    
    // <editor-fold defaultstate="collapsed" desc="Setup editor buffers: BufferedImage-based">
//    void set_editable_bufs()
//    {
//        wrk = copyImage(src);
//        int len = src.getWidth() * src.getHeight();
//
//        // to change image, mod the buffer and do a src.setData(rst)
//        srst = src.getRaster();
//        wrst = wrk.getRaster();
//        if (NBits == 8)
//        {
//            sbuf8 = ((DataBufferByte) srst.getDataBuffer()).getData();
//            wbuf8 = ((DataBufferByte) wrst.getDataBuffer()).getData();
//            Minimum = Maximum = (sbuf8[0] & 0x00ff);
//            for (int k = 0; k < len; ++k)
//            {
//                int px = sbuf8[k] & 0x00ff;
//                if (px < Minimum)
//                {
//                    Minimum = px;
//
//                }
//                if (px > Maximum)
//                {
//                    Maximum = px;
//
//                }
//            }
//        } else if (NBits == 16)
//        {
//            sbuf16 = ((DataBufferUShort) srst.getDataBuffer()).getData();
//            wbuf16 = ((DataBufferUShort) wrst.getDataBuffer()).getData();
//            Minimum = Maximum = (sbuf16[0] & 0x0000ffff);
//            for (int k = 0; k < len; ++k)
//            {
//                int px = sbuf16[k] & 0x0000ffff;
//                if (px < Minimum)
//                {
//                    Minimum = px;
//
//                }
//                if (px > Maximum)
//                {
//                    Maximum = px;
//
//                }
//            }
//        }
//
//// <editor-fold defaultstate="collapsed" desc="Old editing bufs - depracated">
////        rst8 = null;
////        rst16 = null;
////        buf8 = null;
////        buf16 = null;
////
////        // only set these if we are working with a fits file, for now
////        if (!Fits.IsFits(Source))
////            return;
////
////        // grab the BImage buffer
////        // to change image, mod the buffer and do a BImage.setData(rst)
////        if (NBits == 8)
////        {
////            rst8 = BImage.getRaster();
////            buf8 = ((DataBufferByte) rst8.getDataBuffer()).getData();
////        } else if (NBits == 16 || NBits == 32)
////        {
////            rst16 = BImage.getRaster();
////            buf16 = ((DataBufferUShort) rst16.getDataBuffer()).getData();
////        }
//
//// </editor-fold>
//    }
// </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="Constructor to copy a PVImage">
//    public PVImage(PVImage mst)
//    {
//        IsFits = mst.IsFits;
//        Width = mst.Width;
//        Height = mst.Height;
//        NBits = mst.NBits;
//        NSeps = mst.NBits;
//        BZero = mst.BZero;
//        BScale = mst.BScale;
//        RawMinimum = mst.RawMinimum;
//        RawMaximum = mst.RawMaximum;
//        Info = mst.Info;
//        Name = mst.Name;
//        Length = mst.Length;
//        Source = mst.Source;
//
//        if (mst.src != null)
//        {
//            src = copyImage(mst.src);
//            set_editable_bufs();
//        }
//        if (mst.src2 != null)
//        {
//            src2 = copyImage(mst.src2);
//            set_editable_bufs2();
//        }
//    }

    // <editor-fold defaultstate="collapsed" desc="Copy BufferedImage - depracated">
//    public BufferedImage copyBufferedImage()
//    {
//        BufferedImage src = BImage;
//        BufferedImage dst = new BufferedImage(src.getWidth(), src.getHeight(), src.getType());
//        WritableRaster drst = dst.getRaster();
//        if (NBits == 8)
//        {
//            DataBufferByte ddb = (DataBufferByte) drst.getDataBuffer();
//            byte[] dbuf = ddb.getData();
//            System.arraycopy(buf8, 0, dbuf, 0, Length);
//            dst.setData(drst);
//        } else if (NBits == 16 || NBits == 32)
//        {
//            DataBufferUShort ddb = (DataBufferUShort) drst.getDataBuffer();
//            short[] dbuf = ddb.getData();
//            System.arraycopy(buf16, 0, dbuf, 0, Length);
//            dst.setData(drst);
//        }
//        return dst;
//    }
    // </editor-fold>
    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="apply TRC: JAI-based">
    public void applyTRC2(int[] trc, boolean doNotify)
    {
        if (NBits == 8)
        {
            
            for (int k = 0; k < sbuf8.length; ++k)
            {
                int px = sbuf8[k] & 0x00ff;
                wbuf8[k] = (byte) (trc[px] & 0x00ff);
            }
            wrk2.setData(wrst);
        } else if (NBits == 16 || NBits == 32)
        {
            for (int k = 0; k < sbuf16.length; ++k)
            {
                int px = sbuf16[k] & 0x0000ffff;
                wbuf16[k] = (short) (trc[px] & 0x0000ffff);
            }
            wrk2.setData(wrst);
        }

        if (doNotify)
        {
            messenger.sendMessage(ImageMessage.DISPLAY_UPDATED, this, new Object[] { this });
        }
    }
    // </editor-fold>
    
    // <editor-fold defaultstate="collapsed" desc="apply a TRC: BufferedImage-based">
// <editor-fold defaultstate="collapsed" desc="old buffer style applyTRC - depracated">
    //public void applyTRC(int[] trc, boolean doNotify) //    {
    //        if (NBits == 8)
    //        {
    //            if (undo8 == null || undo8.length != buf8.length)
    //                undo8 = new byte[buf8.length];
    //            System.arraycopy(buf8, 0, undo8, 0, buf8.length);
    //
    //            for (int k = 0; k < buf8.length; ++k)
    //            {
    //                int px = Data[k] & 0x00ff;
    //                buf8[k] = (byte) (trc[px] & 0x00ff);
    //            }
    //            BImage.setData(rst8);
    //        }
    //
    //        else if (NBits == 16 || NBits == 32)
    //        {
    //            if (undo16 == null || undo16.length != buf16.length)
    //                undo16 = new short[buf16.length];
    //            System.arraycopy(buf16, 0, undo16, 0, buf16.length);
    //
    //            for (int k = 0; k < buf16.length; ++k)
    //            {
    //                int px = Data[k] & 0x0000ffff;
    //                buf16[k] = (short) (trc[px] & 0x0000ffff);
    //            }
    //            BImage.setData(rst16);
    //        }
    //
    //        if (doNotify)
    //        {
    //            Messenger.SendMessage("DisplayUpdated", this, this);
    //        }
    //    }
    // </editor-fold>
    
//    public void applyTRC(int[] trc, boolean doNotify)
//    {
//        if (NBits == 8)
//        {
//
//            for (int k = 0; k < sbuf8.length; ++k)
//            {
//                int px = sbuf8[k] & 0x00ff;
//                wbuf8[k] = (byte) (trc[px] & 0x00ff);
//            }
//            wrk.setData(wrst);
//        }
//
//        else if (NBits == 16 || NBits == 32)
//        {
//            for (int k = 0; k < sbuf16.length; ++k)
//            {
//                int px = sbuf16[k] & 0x0000ffff;
//                wbuf16[k] = (short) (trc[px] & 0x0000ffff);
//            }
//            wrk.setData(wrst);
//        }
//
//        if (doNotify)
//            Messenger.SendMessage("DisplayUpdated", this, this);
//    }
    // </editor-fold>
    
    public void applyImageOp2(String title, ParameterBlock pb, boolean doNotify)
    {
        pb.addSource(src2);
        PlanarImage pi = JAI.create(title, pb);
        wrk2 = new TiledImage(pi, true);
        
        if (doNotify)
            messenger.sendMessage(ImageMessage.IMAGE_UPDATED, this, new Object[] { this });
    }

    public void applyImageOps(String op1, ParameterBlock pb1, String op2, ParameterBlock pb2, boolean doNotify)
    {
        RenderingHints rh = new RenderingHints(JAI.KEY_BORDER_EXTENDER, BorderExtender.createInstance(BorderExtender.BORDER_COPY));
        pb1.addSource(src2);
        pb2.addSource((PlanarImage)JAI.create(op1, pb1, rh));
        PlanarImage pi = JAI.create(op2, pb2, rh);
        wrk2 = new TiledImage(pi, true);

        if (doNotify)
            messenger.sendMessage(ImageMessage.IMAGE_UPDATED, this, new Object[] { this });
    }
    
//    public void applyImageOp(BufferedImageOp op, boolean doNotify)
//    {
//        op.filter(src, wrk);
//
//        if (doNotify)
//            Messenger.SendMessage("DisplayUpdated", this, this);
//    }


    public void retainMods2()
    {
        undo2 = src2;
        src2 = wrk2;
        set_editable_bufs2();
        messenger.sendMessage(ImageMessage.IMAGE_UPDATED, this, new Object[] { this });
    }
    
//    public void retainMods()
//    {
//        undo = src;
//        src = wrk;
//        set_editable_bufs();
//        Messenger.SendMessage("ImageUpdated", this, this);
//
//// <editor-fold defaultstate="collapsed" desc="old buffer style retainMods - depracated">
////        int minx = 65535;
////        int maxx = 0;
////        if (NBits == 8)
////        {
////            for (int k = 0; k < sbuf8.length; ++k)
////            {
////                int px = buf8[k] & 0x00ff;
////                if (px < minx)
////                {
////                    minx = px;
////
////                }
////                if (px > maxx)
////                {
////                    maxx = px;
////
////                }
////                Data[k] = px;
////            }
////        } else if (NBits == 16)
////        {
////            for (int k = 0; k < buf16.length; ++k)
////            {
////                int px = buf16[k] & 0x0000ffff;
////                if (px < minx)
////                {
////                    minx = px;
////
////                }
////                if (px > maxx)
////                {
////                    maxx = px;
////
////                }
////                Data[k] = px;
////            }
////        }
////        Minimum = minx;
////        Maximum = maxx;
////        Messenger.SendMessage("ImageUpdated", this, this);
//// </editor-fold>
//    }

    public void resetImage2()
    {
        set_editable_bufs2();
        messenger.sendMessage(ImageMessage.IMAGE_UPDATED, this, new Object[] { this });
    }
    
//    public void resetImage()
//    {
//        set_editable_bufs();
//        Messenger.SendMessage("ImageUpdated", this, this);
//
//// <editor-fold defaultstate="collapsed" desc="old buffer style resetImage - depracated">
////        if (NBits == 8)
////        {
////            for (int k=0; k<buf8.length; ++k)
////                buf8[k] = (byte)(Data[k] & 0x00ff);
////            BImage.setData(rst8);
////        }
////        else if (NBits == 16)
////        {
////            for (int k=0; k<buf16.length; ++k)
////                buf16[k] = (short)(Data[k] & 0x0000ffff);
////            BImage.setData(rst16);
////        }
////        Messenger.SendMessage("ImageUpdated", this, this);
//
//// </editor-fold>
//    }

    public TiledImage getImage2()
    {
        return wrk2;
    }

//    public BufferedImage getImage()
//    {
//        return wrk;
//    }

//    public BufferedImage getStretchedBufferedImage()
//    {
//        int[] trc = TRC.stretchTRC(Minimum, Maximum, NBits);
//        applyTRC(trc, false);
//        return wrk;
//    }

    // <editor-fold defaultstate="collapsed" desc="original stretch - depracated">
//    public BufferedImage getStretchedBufferedImage()
//    {
//        BufferedImage bimg = null;
//
//        if (NBits == 8)
//        {
//            bimg = new BufferedImage(Width, Height, BufferedImage.TYPE_BYTE_GRAY);
//            WritableRaster rst = bimg.getRaster();
//            DataBufferByte usbuf = (DataBufferByte) rst.getDataBuffer();
//            byte[] pix = (byte[]) usbuf.getData();
//            double sf = (double) 255 / (double) (Maximum - Minimum);
//            for (int k = 0; k < Length; ++k)
//            {
//                int p = (int) ((double) (Data[k] - Minimum) * sf);
//                pix[k] = (byte) (p & 0x000000FF);
//            }
//            bimg.setData(rst);
//        } else if (NBits == 16)
//        {
//            bimg = new BufferedImage(Width, Height, BufferedImage.TYPE_USHORT_GRAY);
//            WritableRaster rst = bimg.getRaster();
//            DataBufferUShort usbuf = (DataBufferUShort) rst.getDataBuffer();
//            short[] pix = (short[]) usbuf.getData();
//            double sf = (double) 65535 / (double) (Maximum - Minimum);
//            for (int k = 0; k < Length; ++k)
//            {
//                int p = (int) ((double) (Data[k] - Minimum) * sf);
//                pix[k] = (short) (p & 0x0000FFFF);
//            }
//            bimg.setData(rst);
//        } else if (NBits == 32)   // really, this  is the same as 16 bits because BufferedImage has no 32bit iamge model.
//        {
//            bimg = new BufferedImage(Width, Height, BufferedImage.TYPE_USHORT_GRAY);
//            WritableRaster rst = bimg.getRaster();
//            DataBufferUShort usbuf = (DataBufferUShort) rst.getDataBuffer();
//            short[] pix = (short[]) usbuf.getData();
//            double sf = (double) 65535 / (double) (Maximum - Minimum);
//            for (int k = 0; k < Length; ++k)
//            {
//                int p = (int) ((double) (Data[k] - Minimum) * sf);
//                pix[k] = (byte) (p & 0x0000FFFF);
//            }
//            bimg.setData(rst);
//        }
//        return bimg;
//    }
    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="Get stretched rectangle">
    public BufferedImage getStretchedWindow(Rectangle win) throws FITSException
    {
        BufferedImage res = null;
        if (NBits == 8)
        {
            throw new FITSException("Stretching an 8bit window not implemented");
        } else if (NBits == 16)
        {
            res = new BufferedImage(win.width, win.height, BufferedImage.TYPE_USHORT_GRAY);
            WritableRaster rst = res.getRaster();
            DataBufferUShort usbuf = (DataBufferUShort) rst.getDataBuffer();
            short[] pix = (short[]) usbuf.getData();

            // calc min/max
            int p = 0;
            int w = win.width;
            int h = win.height;
            int min = 65535;
            int max = 0;
            int ofs = win.y * Width + win.x;
            for (int y = 0; y < h; ++y, ofs += Width)
            {
                for (int x = 0, k = ofs; x < w; ++x, ++k)
                {
                    if (NBits == 8)
                        p = (sbuf8[k] & 0x00ff);
                    else if (NBits == 16)
                        p = (sbuf16[k] & 0x0000ffff);
                    min = (p < min) ? p : min;
                    max = (p > max) ? p : max;
                }

                // stretch to min/max

            }
            double sf = (double) 65535 / (double) (max - min);
            ofs = win.y * Width + win.x;
            for (int y = 0, kk = 0; y < h; ++y, ofs += Width)
            {
                for (int x = 0, k = ofs; x < w; ++x, ++k, ++kk)
                {
                    if (NBits == 8)
                        p = (sbuf8[k] & 0x00ff);
                    else if (NBits == 16)
                        p = (sbuf16[k] & 0x0000ffff);
                    p = (int) ((double) (p - min) * sf);
                    pix[kk] = (short) (p & 0x0000FFFF);
                }

            }
            res.setData(rst);
        } else if (NBits == 32)
        {
            throw new FITSException("Stretching an 32bit window not implemented");
        }
        return res;
    }
    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="generate a thumbnail image">
    public BufferedImage getThumbnail()
    { return getThumbnail(96); }

    public BufferedImage getThumbnail(int maxdim)
    {
        if (Thumbnail == null)
        {
            try
            {
                if (IsFits)
                {
                    int w = src2.getWidth();
                    int h = src2.getHeight();
                    float sf = (w>h) ? (float)maxdim / (float)w : (float)maxdim / (float)h;
                    ParameterBlock pb = new ParameterBlock();
                    pb.addSource(src2);
                    pb.add(sf);
                    pb.add(sf);
                    pb.add(0f);
                    pb.add(0f);
                    pb.add(new InterpolationNearest());
                    PlanarImage t = JAI.create("scale", pb);

                    int[] trc = TRC.stretchTRC(Minimum, Maximum, NBits);
                    TiledImage tt = new TiledImage(t,true);
                    tt = applyTRC(tt, trc);
                    Thumbnail = tt.getAsBufferedImage();
                 }

                else
                {
                    BufferedImage bi = ImageIO.read(new File(Source));
                    Image timg = new ImageIcon(bi.getScaledInstance(maxdim, -1, Image.SCALE_FAST)).getImage();
                    Thumbnail = new BufferedImage(timg.getWidth(null), timg.getHeight(null), bi.getType());
                    Graphics g = Thumbnail.createGraphics();
                    g.drawImage(timg, 0, 0, null);
                    g.dispose();

                }
            }
            catch (Exception ex)
            {
            }

        }
        return Thumbnail;
    }
    // </editor-fold>

    // <editor-fold defaultstate="collapsed" desc="Basic image processing">
    static public TiledImage applyTRC(TiledImage src, int[] trc)
    {
        WritableRaster srst = src.getWritableTile(0,0);
        DataBuffer db = srst.getDataBuffer();
        if (db.getDataType() == DataBuffer.TYPE_BYTE)
        {
            byte[] pix = ((DataBufferByte)db).getData();
            for (int k = 0; k < pix.length; ++k)
            {
                int px = pix[k] & 0x00ff;
                pix[k] = (byte) (trc[px] & 0x00ff);
            }
        }
        else if (db.getDataType() == DataBuffer.TYPE_USHORT)
        {
            short[] pix = ((DataBufferUShort)db).getData();
            for (int k = 0; k < pix.length; ++k)
            {
                int px = pix[k] & 0x0000ffff;
                pix[k] = (short) (trc[px] & 0x0000ffff);
            }
        }
        src.setData(srst);
        return src;
    }
    
    public BufferedImage Embrossing(BufferedImage bi)
    {
        BufferedImage buff = new BufferedImage(bi.getWidth(), bi.getHeight(), bi.getType());

        Kernel kernel = new Kernel(3, 3, new float[]
                {
                    -2f, 0f, 0f,
                    0f, 1f, 0f,
                    0f, 0f, 2f
                });

        ConvolveOp op = new ConvolveOp(kernel);
        op.filter(bi, buff);

        return buff;
    }

    public BufferedImage Blurring(BufferedImage bi)
    {
        BufferedImage buff = new BufferedImage(bi.getWidth(), bi.getHeight(), bi.getType());

        Kernel kernel = new Kernel(3, 3, new float[]
                {
                    1f / 9f, 1f / 9f, 1f / 9f,
                    1f / 9f, 1f / 9f, 1f / 9f,
                    1f / 9f, 1f / 9f, 1f / 9f
                });

        ConvolveOp op = new ConvolveOp(kernel);
        op.filter(bi, buff);

        return buff;
    }

    public BufferedImage Sharpening(BufferedImage bi)
    {
        BufferedImage buff = new BufferedImage(bi.getWidth(), bi.getHeight(), bi.getType());

        Kernel kernel = new Kernel(3, 3, new float[]
                {
                    -1f, -1f, -1f,
                    -1f, 9f, -1f,
                    -1f, -1f, -1f
                });

        ConvolveOp op = new ConvolveOp(kernel);
        op.filter(bi, buff);

        return buff;
    }
    // </editor-fold>

}

class PVImageException extends Exception
{
    public PVImageException(String msg)
    {
        super(msg);
    }
}